using Unity.Mathematics;
using EarthUtility;
using Geometry;
using System;

namespace EarthGeospatial
{
    public class WebMercatorProjection : IProjection
    {
        public static double MAXIMUM_LATITUDE = mercatorAngleToGeodeticLatitude(Mathd.ONE_PI);
        public static GlobeRectangle MAXIMUM_GLOBE_RECTANGLE = new GlobeRectangle(-Mathd.ONE_PI, -MAXIMUM_LATITUDE, Mathd.ONE_PI, MAXIMUM_LATITUDE);

        Ellipsoid _ellipsoid;
        double _semimajorAxis;
        double _oneOverSemimajorAxis;
        public WebMercatorProjection() : this(Ellipsoid.WGS84) { }
        public WebMercatorProjection(Ellipsoid ellipsoid)
        {
            this._ellipsoid = ellipsoid;
            this._semimajorAxis = ellipsoid.getMaximumRadius();
            this._oneOverSemimajorAxis = 1.0 / ellipsoid.getMaximumRadius();
        }

        public static Rectangle computeMaximumProjectedRectangle(Ellipsoid wGS84)
        {
            throw new NotImplementedException();
        }

        public double3 project(Cartographic cartographic)
        {

            double semimajorAxis = this._semimajorAxis;
            return new double3(
                cartographic.longitude * semimajorAxis, geodeticLatitudeToMercatorAngle(
                    cartographic.latitude) *
                    semimajorAxis,
                cartographic.height);
        }

        public Rectangle project(GlobeRectangle rectangle)
        {
            double3 sw = this.project(rectangle.getSouthwest());
            double3 ne = this.project(rectangle.getNortheast());
            return new Rectangle(sw.x, sw.y, ne.x, ne.y);
        }

        public Cartographic unproject(
            double2 projectedCoordinates)
        {
            double oneOverEarthSemimajorAxis = this._oneOverSemimajorAxis;
            return new Cartographic(
                projectedCoordinates.x * oneOverEarthSemimajorAxis,
              mercatorAngleToGeodeticLatitude(
                    projectedCoordinates.y * oneOverEarthSemimajorAxis),
                0.0);
        }

        Cartographic unproject(double3 projectedCoordinates)
        {
            Cartographic result = this.unproject(new double3(projectedCoordinates));
            result.height = projectedCoordinates.z;
            return result;
        }

        public GlobeRectangle unproject(Rectangle rectangle)
        {
            Cartographic sw = this.unproject(rectangle.getLowerLeft());
            Cartographic ne = this.unproject(rectangle.getUpperRight());
            return new GlobeRectangle(sw.longitude, sw.latitude, ne.longitude, ne.latitude);
        }

        /*static*/
        public static double mercatorAngleToGeodeticLatitude(double mercatorAngle)
        {
            return Mathd.PI_OVER_TWO - 2.0 * math.atan(math.exp(-mercatorAngle));
        }

        /*static*/
        public static double geodeticLatitudeToMercatorAngle(
 double latitude)
        {
            // Clamp the latitude coordinate to the valid Mercator bounds.
            latitude = math.clamp(latitude, -MAXIMUM_LATITUDE, MAXIMUM_LATITUDE);
            double sinLatitude = math.sin(latitude);
            return 0.5 * math.log((1.0 + sinLatitude) / (1.0 - sinLatitude));
        }

    }
}